﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;

using Microsoft.WowAddonStudio.Lua.AST;
using Microsoft.WowAddonStudio.Lua.Model;

namespace Microsoft.WowAddonStudio.Lua.Parser
{
    /// <summary>
    /// A <see cref="Parser"/> that provides lexical scoping.
    /// </summary>
    /// <typeparam name="TScope"></typeparam>
    public class ScopedParser<TScope> : Parser where TScope : Scope, new()
    {
        private readonly Stack<TScope> scopes = new Stack<TScope>();
        private readonly TScope globalScope;
        private TScope lastScope;

        /// <summary>
        /// Initializes a new instance of the <see cref="Parser"/> class.
        /// </summary>
        public ScopedParser()
        {
            scopes.Push(new TScope());
            globalScope = scopes.Peek();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Parser"/> class.
        /// </summary>
        /// <param name="globalScope">The global scope.</param>
        public ScopedParser(TScope globalScope)
        {
            if (globalScope == null)
                throw new ArgumentNullException("globalScope");

            this.globalScope = globalScope;
            scopes.Push(globalScope);
        }

        /// <summary>
        /// Finds the scope that contains the given location.
        /// </summary>
        /// <param name="line">The line number of the location.</param>
        /// <param name="column">The column index of the location.</param>
        /// <returns>An instance of the <see cref="Scope"/> class.</returns>
        public Scope FindScope(int line, int column)
        {
            return this.Scope.FindScope(line, column);
        }

        /// <summary>
        /// Gets the global scope.
        /// </summary>
        public TScope GlobalScope
        {
            get { return globalScope; }
        }

        /// <summary>
        /// Gets the scope associated with the parser.
        /// </summary>
        public TScope Scope
        {
            get { return this.Chunk != null ? (TScope)this.Chunk.Scope : null; }
        }

        /// <summary>
        /// Enter into a new lexical scope.
        /// </summary>
        protected override void EnterScope()
        {
            scopes.Push((TScope)Activator.CreateInstance(typeof(TScope), this.CurrentScope));

            if (Tracing)
                Trace.WriteLine("Entered scope. Level: " + this.CurrentScope.Level);
        }

        /// <summary>
        /// Leaves the current lexical scope.
        /// </summary>
        protected override void LeaveScope()
        {
            if (scopes.Count <= 1)
                throw new InvalidOperationException("Cannot leave global scope.");

            // Pop the scope being left and initialize it
            lastScope = scopes.Pop();

            if (Tracing)
                Trace.WriteLine("Left scope. Level: " + (this.CurrentScope != null ? this.CurrentScope.Level : 0));
        }

        /// <summary>
        /// Defines the location of the last scope.
        /// </summary>
        /// <param name="location"></param>
        protected override void DefineScope(LexLocation location)
        {
            if (location == null)
                throw new ArgumentNullException("location");

            lastScope.Location = location;
        }

        /// <summary>
        /// Associates the nodes with their scope during a reduce step.
        /// </summary>
        /// <param name="action_nr">The index of the action.</param>
        protected override void DoAfterAction(int action_nr)
        {
            if (yyval.Node != null)
            {
                // Blocks should be associated with the same scope as their children
                if (yyval.Node is Block || yyval.Node is Chunk)
                {
                    // Set the block's scope to the last scope we left
                    yyval.Node.Scope = lastScope;
                }
                else
                    yyval.Node.Scope = this.CurrentScope;
            }
        }

        /// <summary>
        /// Gets the current scope.
        /// </summary>
        private Scope CurrentScope
        {
            get { return scopes.Count > 0 ? scopes.Peek() : null; }
        }

        public new static TScope Parse(Stream stream)
        {
            // Create a parser
            var parser = Parser.CreateParser<ScopedParser<TScope>>(stream);

            // Parse the source
            parser.Parse();

            // Return the global scope
            return parser.GlobalScope;
        }

        public new static TScope Parse(string source)
        {
            // Create a parser
            var parser = Parser.CreateParser<ScopedParser<TScope>>(source);

            // Parse the source
            parser.Parse();

            // Return the global scope
            return parser.GlobalScope;
        }
    }
}
